--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 4adb3fe9aae9a50f9981949103c56957ba97a2bd
Parents : 0be9ccd
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-05-09T19:00:50-05:00
chore(ci): update build scripts and release workflow
Changes
5 files changed, 218 insertions(+), 41 deletions(-)
Diff
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index dbba2a66..c994aa0c 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -81,12 +81,10 @@ jobs:
run_unit_tests: true
linux-release:
- name: Linux release assets
+ name: Linux release assets (x64)
needs: frontend
runs-on: ubuntu-latest
timeout-minutes: 120
- outputs:
- hashes: ${{ steps.slsa-hashes.outputs.hashes }}
permissions:
contents: read
actions: write
@@ -156,9 +154,116 @@ jobs:
- name: Build release-assets
run: bash scripts/ci/github-build-linux-release-assets.sh
+ - name: Upload Linux release artifact (x64)
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: meshchatx-linux-release-x64-${{ github.ref_name }}-${{ github.run_id }}
+ path: release-assets/
+ if-no-files-found: error
+ retention-days: 30
+
+ linux-release-arm64:
+ name: Linux release assets (arm64)
+ needs: frontend
+ runs-on: ubuntu-24.04-arm
+ timeout-minutes: 120
+ permissions:
+ contents: read
+ actions: write
+ env:
+ FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }}
+ MESHCHATX_FRONTEND_PREBUILT: "1"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
+
+ - name: Set up Python
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
+ with:
+ python-version: ${{ env.PYTHON_VERSION }}
+
+ - name: Install Poetry (PyPI pin)
+ env:
+ POETRY_VERSION: ${{ env.POETRY_VERSION }}
+ run: bash scripts/ci/github-install-poetry.sh
+
+ - name: Cache Poetry downloads
+ uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57
+ with:
+ path: ~/.cache/pypoetry
+ key: ${{ runner.os }}-pypoetry-${{ hashFiles('poetry.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-pypoetry-
+
+ - name: Enable pnpm (corepack)
+ run: corepack enable && corepack prepare "pnpm@${PNPM_VERSION}" --activate
+
+ - name: Set up Node
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Linux packaging APT dependencies
+ run: bash scripts/ci/github-apt-linux-packaging.sh
+
+ - name: Install project dependencies
+ run: bash scripts/ci/github-install-deps.sh
+
+ - name: Download frontend artifact
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
+ with:
+ name: ${{ env.FRONTEND_ARTIFACT_NAME }}
+ path: meshchatx/public
+
+ - name: Verify frontend artifact contents
+ run: |
+ set -euo pipefail
+ test -f meshchatx/public/index.html
+ test -d meshchatx/public/assets
+ test -d meshchatx/public/reticulum-docs-bundled/current
+
+ - name: Build release-assets
+ run: bash scripts/ci/github-build-linux-release-assets.sh
+
+ - name: Upload Linux release artifact (arm64)
+ uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
+ with:
+ name: meshchatx-linux-release-arm64-${{ github.ref_name }}-${{ github.run_id }}
+ path: release-assets/
+ if-no-files-found: error
+ retention-days: 30
+
+ collect-linux-slsa-subjects:
+ name: SLSA subjects + cosign (Linux)
+ needs: [linux-release, linux-release-arm64]
+ if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ outputs:
+ hashes: ${{ steps.hash.outputs.hashes }}
+ permissions:
+ contents: read
+ actions: write
+ id-token: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
+
+ - name: Download x64 artifacts
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
+ with:
+ name: meshchatx-linux-release-x64-${{ github.ref_name }}-${{ github.run_id }}
+ path: release-assets
+
+ - name: Download arm64 artifacts
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
+ with:
+ name: meshchatx-linux-release-arm64-${{ github.ref_name }}-${{ github.run_id }}
+ path: release-assets
+
- name: SLSA subject hashes
- id: slsa-hashes
- if: startsWith(github.ref, 'refs/tags/')
+ id: hash
run: bash scripts/ci/github-slsa-hashes-release-assets.sh
- name: SLSA attestations (cosign)
@@ -187,14 +292,6 @@ jobs:
sh scripts/ci/attest-release-assets.sh ./release-assets
rm -f /tmp/cosign.key
- - name: Upload Linux release artifact
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
- with:
- name: meshchatx-linux-release-${{ github.ref_name }}-${{ github.run_id }}
- path: release-assets/
- if-no-files-found: error
- retention-days: 30
-
flatpak:
name: Flatpak (electron-forge)
needs: frontend
@@ -467,7 +564,7 @@ jobs:
slsa-provenance-linux:
name: SLSA provenance (Linux)
- needs: [linux-release]
+ needs: [collect-linux-slsa-subjects]
if: startsWith(github.ref, 'refs/tags/')
permissions:
id-token: write
@@ -475,7 +572,7 @@ jobs:
actions: read
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
- base64-subjects: ${{ needs.linux-release.outputs.hashes }}
+ base64-subjects: ${{ needs.collect-linux-slsa-subjects.outputs.hashes }}
upload-assets: false
provenance-name: meshchatx-linux-${{ github.ref_name }}.intoto.jsonl
@@ -541,6 +638,8 @@ jobs:
name: Draft GitHub release (all assets + SLSA)
needs:
- linux-release
+ - linux-release-arm64
+ - collect-linux-slsa-subjects
- slsa-provenance-linux
- build-release
- slsa-provenance-desktop
@@ -553,6 +652,8 @@ jobs:
!cancelled() &&
startsWith(github.ref, 'refs/tags/') &&
needs.linux-release.result == 'success' &&
+ needs.linux-release-arm64.result == 'success' &&
+ needs.collect-linux-slsa-subjects.result == 'success' &&
needs.slsa-provenance-linux.result == 'success' &&
needs.build-release.result == 'success' &&
needs.collect-desktop-slsa-subjects.result == 'success' &&
@@ -597,10 +698,16 @@ jobs:
echo "track=${track}" >> "${GITHUB_OUTPUT}"
echo "Resolved tag ${GITHUB_REF_NAME} -> track=${track}"
- - name: Download Linux release assets
+ - name: Download Linux release assets (x64)
+ uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
+ with:
+ name: meshchatx-linux-release-x64-${{ github.ref_name }}-${{ github.run_id }}
+ path: upload
+
+ - name: Download Linux release assets (arm64)
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
with:
- name: meshchatx-linux-release-${{ github.ref_name }}-${{ github.run_id }}
+ name: meshchatx-linux-release-arm64-${{ github.ref_name }}-${{ github.run_id }}
path: upload
- name: Download Windows dist
diff --git a/scripts/build-backend.js b/scripts/build-backend.js
index 678fccba..0c4ca371 100755
--- a/scripts/build-backend.js
+++ b/scripts/build-backend.js
@@ -199,12 +199,50 @@ function shouldRefreshLicenseArtifacts(repoRoot) {
return newestInput >= oldestOutput;
}
+function verifyBinaryArchitecture(buildDir, expectedArch, targetName) {
+ const binaryPath = path.join(buildDir, targetName);
+ if (!fs.existsSync(binaryPath)) {
+ console.warn(`Binary not found at ${binaryPath}, skipping architecture verification.`);
+ return true;
+ }
+
+ let output = "";
+ try {
+ const result = spawnSync("file", ["--brief", "--no-pad", binaryPath], {
+ encoding: "utf-8",
+ shell: false,
+ });
+ if (result.error || result.status !== 0) {
+ console.warn(`Could not verify binary architecture: ${result.error?.message || `exit ${result.status}`}`);
+ return true;
+ }
+ output = result.stdout.toLowerCase();
+ } catch (e) {
+ console.warn(`Architecture verification failed: ${e.message}`);
+ return true;
+ }
+
+ const isArm64 = output.includes("aarch64") || output.includes("arm64");
+ const isX64 = output.includes("x86-64") || output.includes("x86_64") || output.includes("amd64");
+
+ if (expectedArch === "arm64" && !isArm64) {
+ console.error(`Architecture mismatch: expected arm64 but binary is not arm64 (${output.trim()}).`);
+ return false;
+ }
+ if (expectedArch === "x64" && !isX64) {
+ console.error(`Architecture mismatch: expected x64 but binary is not x64 (${output.trim()}).`);
+ return false;
+ }
+ return true;
+}
+
try {
const platform = process.env.PLATFORM || process.platform;
const arch = process.env.ARCH || process.arch;
const isWin = platform === "win32" || platform === "win";
const isDarwin = platform === "darwin";
const targetName = isWin ? "ReticulumMeshChatX.exe" : "ReticulumMeshChatX";
+ const rosettaX64 = isDarwin && arch === "x64" && process.arch === "arm64";
let platformFolder = "linux";
if (isWin) {
@@ -215,6 +253,18 @@ try {
const buildDirRelative = `build/exe/${platformFolder}-${arch}`;
const buildDir = path.join(__dirname, "..", buildDirRelative);
+ if (arch !== process.arch && !rosettaX64 && !process.env.PYTHON_CMD) {
+ console.error(
+ `Cross-compilation detected (host: ${process.arch}, target: ${arch}).\n` +
+ `cx_Freeze produces binaries for the architecture of the Python interpreter it runs under.\n` +
+ `To build the backend for ${arch}, you must either:\n` +
+ ` - Build natively on ${arch} hardware (or in a VM/container of that architecture).\n` +
+ ` - Set PYTHON_CMD to a ${arch} Python interpreter (with Poetry dependencies installed).\n` +
+ ` - Use Docker with QEMU/binfmt support (e.g., docker run --platform ${platformFolder}/${arch}).`
+ );
+ process.exit(1);
+ }
+
// Allow overriding the python command
const pythonCmd = process.env.PYTHON_CMD || "poetry run python";
@@ -237,7 +287,6 @@ try {
let spawnCmd = cmd;
let spawnArgs = licensesArgs;
- const rosettaX64 = isDarwin && arch === "x64" && process.arch === "arm64";
if (rosettaX64) {
spawnCmd = "arch";
spawnArgs = ["-x86_64", cmd, ...licensesArgs];
@@ -278,6 +327,9 @@ try {
if (isDarwin) {
stripPythonBytecodeArtifacts(buildDir);
}
+ if (!verifyBinaryArchitecture(buildDir, arch, targetName)) {
+ process.exit(1);
+ }
const manifestPath = path.join(buildDir, "backend-manifest.json");
const skipManifest =
process.env.MESHCHATX_SKIP_BACKEND_MANIFEST === "1" ||
diff --git a/scripts/ci/docker-build-entry.sh b/scripts/ci/docker-build-entry.sh
index cbc2d750..320e0949 100644
--- a/scripts/ci/docker-build-entry.sh
+++ b/scripts/ci/docker-build-entry.sh
@@ -26,7 +26,12 @@ if ! command -v node >/dev/null 2>&1; then
fi
TASK_VER="${TASK_VERSION:-3.46.4}"
-curl -fsSL "https://github.com/go-task/task/releases/download/v${TASK_VER}/task_linux_amd64.tar.gz" \
+_TASK_ARCH="$(uname -m)"
+case "$_TASK_ARCH" in
+ x86_64) _TASK_ARCH="amd64" ;;
+ aarch64) _TASK_ARCH="arm64" ;;
+esac
+curl -fsSL "https://github.com/go-task/task/releases/download/v${TASK_VER}/task_linux_${_TASK_ARCH}.tar.gz" \
| tar xz -C /usr/local/bin task
corepack enable
diff --git a/scripts/ci/github-apt-linux-packaging.sh b/scripts/ci/github-apt-linux-packaging.sh
index 317e640d..e022aa3d 100644
--- a/scripts/ci/github-apt-linux-packaging.sh
+++ b/scripts/ci/github-apt-linux-packaging.sh
@@ -5,16 +5,14 @@ set -euo pipefail
# shellcheck source=scripts/ci/priv.sh
. "$(dirname "$0")/priv.sh"
-run_priv dpkg --add-architecture i386 || true
+_HOST_ARCH="$(uname -m)"
+if [ "$_HOST_ARCH" = "x86_64" ]; then
+ run_priv dpkg --add-architecture i386 || true
+fi
run_priv apt-get update -y
-run_priv apt-get install -y --no-install-recommends \
- patchelf \
- libopusfile0 \
- espeak-ng \
- zip \
- rpm \
- elfutils \
- fakeroot \
- file \
- libc6:i386 \
- libstdc++6:i386
+
+_PKGS="patchelf libopusfile0 espeak-ng zip rpm elfutils fakeroot file"
+if [ "$_HOST_ARCH" = "x86_64" ]; then
+ _PKGS="$_PKGS libc6:i386 libstdc++6:i386"
+fi
+run_priv apt-get install -y --no-install-recommends $_PKGS
diff --git a/scripts/ci/github-build-linux-release-assets.sh b/scripts/ci/github-build-linux-release-assets.sh
index beb8cc98..33ed490a 100644
--- a/scripts/ci/github-build-linux-release-assets.sh
+++ b/scripts/ci/github-build-linux-release-assets.sh
@@ -12,23 +12,38 @@ cd "$ROOT"
mkdir -p release-assets
+HOST_ARCH="$(uname -m)"
+case "$HOST_ARCH" in
+ x86_64) NATIVE_ARCH="x64" ;;
+ aarch64|arm64) NATIVE_ARCH="arm64" ;;
+ *) NATIVE_ARCH="$HOST_ARCH" ;;
+esac
+
if [ "${SKIP_WHEEL:-0}" != 1 ]; then
- echo "Building Python wheel..."
- task build:wheel
+ if [ "$NATIVE_ARCH" = "x64" ]; then
+ echo "Building Python wheel..."
+ task build:wheel
+ else
+ echo "Skipping wheel on $NATIVE_ARCH runner (pure-Python wheel built on x64)."
+ fi
else
echo "Skipping wheel (SKIP_WHEEL=1)."
fi
if [ "${SKIP_ELECTRON:-0}" != 1 ]; then
- echo "Electron linux x64..."
- pnpm run dist:linux-x64
-
- echo "Electron linux arm64..."
- pnpm run dist:linux-arm64
+ if [ "$NATIVE_ARCH" = "x64" ]; then
+ echo "Electron linux x64..."
+ pnpm run dist:linux-x64
+ elif [ "$NATIVE_ARCH" = "arm64" ]; then
+ echo "Electron linux arm64..."
+ pnpm run dist:linux-arm64
+ fi
- echo "RPM (best-effort)..."
- if ! task dist:fe:rpm; then
- echo "RPM build failed or skipped; continuing." >&2
+ if [ "$NATIVE_ARCH" = "x64" ]; then
+ echo "RPM (best-effort)..."
+ if ! task dist:fe:rpm; then
+ echo "RPM build failed or skipped; continuing." >&2
+ fi
fi
else
echo "Skipping Electron packages (SKIP_ELECTRON=1)."
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────